這篇會講解怎麼樣用 DOM 的 parser 把 RSS 資訊拿出來,首先我們可以先 new 一個 DocumentBuilder
val builder = DocumentBuilderFactory.newInstance().newDocumentBuilder()
接著把 XML 字串塞進去 builder
裡,塞之前我們要先轉成 ByteArrayInputStream
,就可以拿到一個 document 。
val document = builder.parse(xml.byteInputStream())
我們可以從 document 裡面拿到指定 tag 的節點列表,下方就是拿 <channel>
底下的子節點列表。
const val CHANNEL = "channel"
val nodeList = document.getElementsByTagName(CHANNEL)
拿到節點列表之後我們就可以對節點做操作了!但這邊因為 RSS 格式中預設只有一個 <channel>
所以我們可以寫一個 parserChannel
的 function 。從 nodeList 拿出來的 node 要轉型成 Element
才可以進行操作。這個 function 的第二個參數 action
是一個 lambda ,讓使用者決定拿到 channel 的 element 後要進行的動作。
inline fun <T> parseChannel(xml: String, action: Element.() -> T): T {
val builder = DocumentBuilderFactory.newInstance().newDocumentBuilder()
val document = builder.parse(xml.byteInputStream())
document.documentElement.normalize()
val nodeList = document.getElementsByTagName(CHANNEL)
var result: T? = null
if (nodeList?.length == 1) {
val element = nodeList.item(0) as? Element
element?.let {
result = action(it)
}
}
return result ?: throw IllegalArgumentException("No valid channel tag in the RSS feed.")
}
前一篇的文章有提到 DOM parser 有個特色就是可以跨層查詢,但我們怎麼指定我們要查詢哪一個 tag 底下的值?其實可以透過指定 parent tag 的方式去查找。這是為了解決一個可能遇到的問題:假如我們今天要找 <channel>
tag 底下的 <title>
,這個 title 可能存在於 <channel>
底下,但也可能存在於 <image>
底下。
<channel>
<title>channel title</title>
<image>
<link>http://channel.image.link</link>
<title>channel image title</title>
</image>
</channel>
為了解決上面情境發生的問題,我可以寫一個 Element
的 extension function ,讓我們可以取某個 tag 的值出來,但可以指定 parent 的 tag name 。
fun Element.readString(name: String, parentTag: String? = null): String? {
val nodeList = getElementsByTagName(name)
if (parentTag == null) {
return nodeList.item(0)?.textContent
} else {
for (i in 0 until nodeList.length) {
val e = nodeList.item(i) as? Element ?: continue
val parent = e.parentNode as? DeferredElementImpl
if (parent?.tagName != parentTag) continue
return e.textContent
}
return null
}
}